package com.gee.spring.cloud.aerospace.simulate.core.satellite.telemetry;

import cn.hutool.core.util.ArrayUtil;
import com.gee.spring.cloud.aerospace.simulate.core.domain.SateTmPkgDto;
import com.gee.spring.cloud.aerospace.simulate.core.domain.SatelliteTelemetryDto;
import com.gee.spring.cloud.aerospace.simulate.core.domain.TelemetryNode;
import com.gee.spring.cloud.aerospace.simulate.core.domain.TelemetryNodeDto;
import com.gee.spring.cloud.aerospace.simulate.core.util.TelemetryNodeUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author gepengjun
 * @since 2024/7/4 10:23
 */
public class TmFrameGenerator {

    private SatelliteTelemetryDto satelliteTelemetryDto;

    private Integer realRatio;

    private Integer delayRatio;

    private byte[] vc;

    //星上时时间偏移量,单位秒
    private long sateTimeOffset;

    //包识别  参数代号  参数值
    private final Map<String, String> tmParamMap = new HashMap<>();

    private final AtomicLong generateSeq = new AtomicLong();

    private final LinkedBlockingQueue<SateTmPkgDto> realSateTmPkgDtoQueue = new LinkedBlockingQueue<>();

    private final LinkedBlockingQueue<SateTmPkgDto> delaySateTmPkgDtoQueue = new LinkedBlockingQueue<>();

    private byte[] dataAreaRemaining;

    private Integer lengthBeforeFirstPkgHead;

    //数据域全填充
    private Boolean dataAreaAllFilled = false;

    private Boolean dataAreaHasPkgHead = true;

    public byte[] generate(){
        generateSeq.getAndIncrement();
        List<TelemetryNodeDto> frameNodeList = satelliteTelemetryDto.getFrameNodeList();
        frameNodeList.stream()
                .sorted(Comparator.comparingInt(TelemetryNode::getSiblingCalOrder))
                .forEach(telemetryNode -> {
                    TelemetryNodeUtil.calNodeValue(this, telemetryNode, frameNodeList);
                });
        ByteBuf byteBuf = Unpooled.buffer();
        frameNodeList.stream()
                .sorted(Comparator.comparingInt(TelemetryNode::getSiblingAssembleOrder))
                .forEach(telemetryNode -> byteBuf.writeBytes(telemetryNode.getBytes()));
        byte[] bytes = ByteBufUtil.getBytes(byteBuf);
        byteBuf.release();
        return bytes;
    }

    public byte[] generateDataAreaValue(int dataAreaLength){
        dataAreaAllFilled = true;
        dataAreaHasPkgHead = true;
        lengthBeforeFirstPkgHead = null;
        LinkedBlockingQueue<SateTmPkgDto> sateTmPkgDtoQueue;
        if (delayed()){
            sateTmPkgDtoQueue = delaySateTmPkgDtoQueue;
        }else {
            sateTmPkgDtoQueue = realSateTmPkgDtoQueue;
        }

        ByteBuf byteBuf = Unpooled.buffer();
        if (ArrayUtil.isNotEmpty(dataAreaRemaining)){
            int remainingLength = dataAreaRemaining.length;
            dataAreaAllFilled = false;
            if (remainingLength < dataAreaLength){
                byteBuf.writeBytes(dataAreaRemaining);
                lengthBeforeFirstPkgHead = remainingLength;
            }else {
                dataAreaHasPkgHead = false;
                int newRemainingLength = dataAreaLength - remainingLength;
                byte[] newRemaining = new byte[newRemainingLength];
                System.arraycopy(dataAreaRemaining, dataAreaLength - 1, newRemaining, 0, newRemainingLength);
                byteBuf.writeBytes(dataAreaRemaining, 0 , dataAreaLength);
                dataAreaRemaining = newRemaining;
                byte[] result = ByteBufUtil.getBytes(byteBuf);
                byteBuf.release();
                return result;
            }
        }
        do {
            int toWriteLength = dataAreaLength - byteBuf.readableBytes();
            if (sateTmPkgDtoQueue.isEmpty()){
                int i = 0;
                while (i < toWriteLength){
                    byteBuf.writeByte(satelliteTelemetryDto.getFillByte());
                    i++;
                }
            }else {
                dataAreaAllFilled = false;
                SateTmPkgDto sateTmPkgDto = sateTmPkgDtoQueue.poll();
                byte[] pkgData = sateTmPkgDto.getData();
                int pkgLength = pkgData.length;
                if (byteBuf.readableBytes() + pkgLength < dataAreaLength){
                    byteBuf.writeBytes(pkgData);
                }else {
                    int newRemainingLength = pkgLength - toWriteLength;
                    byte[] newRemaining = new byte[newRemainingLength];
                    System.arraycopy(pkgData, toWriteLength - 1, newRemaining, 0, newRemainingLength);
                    dataAreaRemaining = newRemaining;
                    byteBuf.writeBytes(pkgData, 0 , toWriteLength);
                }
            }
        } while (byteBuf.readableBytes() < dataAreaLength);
        byte[] result = ByteBufUtil.getBytes(byteBuf);
        byteBuf.release();
        return result;
    }

    public boolean delayed() {
        if (realRatio == null){
            return true;
        }else if (delayRatio == null){
            return false;
        }
        return generateSeq.get() % (realRatio + delayRatio) <= realRatio;
    }

    public Map<String, String> getTmParamMap() {
        return tmParamMap;
    }

    public AtomicLong getGenerateSeq() {
        return generateSeq;
    }

    public SatelliteTelemetryDto getSatelliteTelemetryDto() {
        return satelliteTelemetryDto;
    }

    public long getRealRatio() {
        return realRatio;
    }

    public long getDelayRatio() {
        return delayRatio;
    }

    public byte[] getVc() {
        return vc;
    }

    public byte[] getDataAreaRemaining() {
        return dataAreaRemaining;
    }

    public Integer getLengthBeforeFirstPkgHead() {
        return lengthBeforeFirstPkgHead;
    }

    public Boolean getDataAreaAllFilled() {
        return dataAreaAllFilled;
    }

    public Boolean getDataAreaHasPkgHead() {
        return dataAreaHasPkgHead;
    }

    public long getSateTimeOffset() {
        return sateTimeOffset;
    }
}
